package jmine.tec.subscription.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.subscription.Publication;
import jmine.tec.subscription.Publisher;
import jmine.tec.subscription.SubscriptionSettings;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * Stub para implementacao de um {@link Publisher}. Requer a definicao do tipo concreto de {@link Publication}. Implementacoes desta classe
 * nao devem fazer cache das {@link Publication}.
 * 
 * @author takeshi
 * @param <P> o tipo concreto da {@link Publication}
 */
public abstract class AbstractPublisher<P extends Publication> implements Publisher<P>, ApplicationContextAware {

    /**
     * @author takeshi
     * @param <P> extends {@link Publication}
     */
    private static final class PartialPublicationTransactionSynchronization<P extends Publication> extends
            TransactionSynchronizationAdapter {

        private final PartialPublication<P> p;

        private final SubscriptionSettings settings;

        private final EventRouter eventRouter;

        private final Publisher<P> publisher;

        /**
         * C'tor
         * 
         * @param p {@link PartialPublication}
         * @param settings {@link SubscriptionSettings}
         * @param eventRouter {@link EventRouter}
         * @param publisher {@link Publisher}
         */
        public PartialPublicationTransactionSynchronization(PartialPublication<P> p, SubscriptionSettings settings,
                EventRouter eventRouter, Publisher<P> publisher) {
            super();
            this.p = p;
            this.settings = settings;
            this.eventRouter = eventRouter;
            this.publisher = publisher;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                eventRouter.broadcastPartialPublication(settings, this.publisher, p);
            }
        }
    }

    /**
     * @author takeshi
     * @param <P>
     */
    private static final class BroadcastEventTransactionSynchronization<P extends Publication> extends TransactionSynchronizationAdapter {
        private final P p;

        private final SubscriptionSettings settings;

        private final EventRouter eventRouter;

        private final Publisher<P> publisher;

        /**
         * C'tor
         * 
         * @param p {@link PartialPublication}
         * @param settings {@link SubscriptionSettings}
         * @param eventRouter {@link EventRouter}
         * @param publisher {@link Publisher}
         */

        public BroadcastEventTransactionSynchronization(P p, SubscriptionSettings settings, EventRouter eventRouter, Publisher<P> publisher) {
            super();
            this.p = p;
            this.settings = settings;
            this.eventRouter = eventRouter;
            this.publisher = publisher;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                eventRouter.broadcast(settings, this.publisher, p);
            }
        }
    }

    private EventRouter eventRouter;

    private final Class<P> publicationType;

    private SubscriptionManagerImplementor subscriptionManager;

    /**
     * C'tor
     * 
     * @param publicationType {@link Class} of P
     */
    public AbstractPublisher(Class<P> publicationType) {
        super();
        this.publicationType = publicationType;
        verifyUnmodifiable(publicationType);
    }

    /**
     * Verifica que o tipo da publicacao
     * 
     * @param publicationType2 {@link Class}
     */
    private static void verifyUnmodifiable(Class<?> publicationType2) {
        Class<?> theClass = publicationType2;
        if (theClass.isInterface()) {
            // nothing to do
            return;
        }
        while (theClass != Object.class) {
            if (publicationType2.getAnnotation(Unmodifiable.class) == null) {
                throw new IllegalArgumentException("o tipo da publication deve ser unmodifiable: " + publicationType2);
            }
            theClass = theClass.getSuperclass();
        }
    }

    /**
     * {@inheritDoc}
     */
    public final Map<SubscriptionSettings, Publication> renewPublications(Collection<? extends SubscriptionSettings> settings) {
        Map<SubscriptionSettings, Publication> map = new HashMap<SubscriptionSettings, Publication>(settings.size());
        for (SubscriptionSettings subscriptionSettings : settings) {
            Publication publication = this.newPublication(subscriptionSettings);
            map.put(subscriptionSettings, publication);
        }
        return map;
    }

    /**
     * publica um novo 'publication'
     * 
     * @param settings {@link SubscriptionSettings}
     * @param p P
     */
    public final void publish(final SubscriptionSettings settings, final P p) {
        if (TransactionSynchronizationManager.isActualTransactionActive() && TransactionSynchronizationManager.isSynchronizationActive()) {
            // schedule partial publication as transactionSynchronization
            TransactionSynchronizationManager.registerSynchronization(new BroadcastEventTransactionSynchronization<P>(p, settings,
                    this.eventRouter, this));
        } else {
            this.eventRouter.broadcast(settings, this, p);
        }
    }

    /**
     * publica um novo 'publication' parcial
     * 
     * @param settings {@link SubscriptionSettings}
     * @param p P
     */
    public final void publishPartialPublication(final SubscriptionSettings settings, final PartialPublication<P> p) {
        if (TransactionSynchronizationManager.isActualTransactionActive() && TransactionSynchronizationManager.isSynchronizationActive()) {
            // schedule partial publication as transactionSynchronization
            TransactionSynchronizationManager.registerSynchronization(new PartialPublicationTransactionSynchronization<P>(p, settings,
                    this.eventRouter, this));
        } else {
            this.eventRouter.broadcastPartialPublication(settings, this, p);
        }
    }

    /**
     * @param sm the {@link SubscriptionManagerImplementor}
     */
    public final void setSubscriptionManager(SubscriptionManagerImplementor sm) {
        this.eventRouter = new DirectEventRouter(sm);
        this.subscriptionManager = sm;
    }

    /**
     * {@inheritDoc}
     */
    public SubscriptionSettings newSubscriptionSettings(Map<String, Object> map) {
        String[] keys = this.allowedMapKeys();
        if (keys != null) {
            Map<String, Object> copy = new HashMap<String, Object>(keys.length);
            for (String key : keys) {
                Object val = map.get(key);
                if (val != null) {
                    copy.put(key, val);
                }
            }
            return new PlainSubscriptionSettings(map);
        }
        return new PlainSubscriptionSettings(map);
    }

    /**
     * Devolve as chaves que podem ser usadas nos {@link SubscriptionSettings}. Se este metodo devolver <code>null</code>, todas as chaves
     * sao permitidas.
     * 
     * @return String[]
     */
    protected String[] allowedMapKeys() {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public final Class<P> publicationType() {
        return this.publicationType;
    }

    /**
     * {@inheritDoc}
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (this.eventRouter == null) {
            this.eventRouter = new ApplicationEventRouter(applicationContext);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void evict(SubscriptionSettings settings) {
    }

    /**
     * @return {@link SubscriptionManagerImplementor}
     */
    public final SubscriptionManagerImplementor getSubscriptionManager() {
        return subscriptionManager;
    }
}
